package com.niuwa.p2p.server.utils.plugins;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.executor.ExecutorException;
import org.apache.ibatis.executor.statement.BaseStatementHandler;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.property.PropertyTokenizer;
import org.apache.ibatis.scripting.xmltags.ForEachSqlNode;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.TypeHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.apache.poi.ss.formula.functions.T;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.niuwa.p2p.core.mvc.entity.PageComponent;
import com.niuwa.p2p.server.utils.ReflectUtil;

public class PagePlugin implements IPlugin {

	Logger logger = LoggerFactory.getLogger(this.getClass());

	private Map<String, String> propertyMap = new HashMap<String, String>();
	private String dialect = null;

	@Override
	public void init(String propertyParameter) throws Exception {
		if (propertyParameter == null)
			throw new ExecutorException("plugin [PagePlugin] --> paramter dialect is null!");
		String[] pms = propertyParameter.split(";");
		for (String str : pms) {
			str = str.trim();
			if (str.length() == 0)
				continue;
			String[] kv = str.split("=");
			if (kv.length == 0)
				continue;
			propertyMap.put(kv[0], kv[1]);
		}
		dialect = propertyMap.get("dialect");
		if (dialect == null)
			throw new ExecutorException("plugin [PagePlugin] --> paramter dialect is null!");
	}

	@Override
	public Object intercept(Invocation ivk) throws Exception {
		Object target = ivk.getTarget();
		if (!"prepare".equals(ivk.getMethod().getName()) || !(target instanceof RoutingStatementHandler)) {
			return ivk.proceed();
		}
		RoutingStatementHandler statementHandler = (RoutingStatementHandler) target;
		BaseStatementHandler delegate = (BaseStatementHandler) ReflectUtil.getValueByFieldName(statementHandler, "delegate");
		MappedStatement mappedStatement = (MappedStatement) ReflectUtil.getValueByFieldName(delegate, "mappedStatement");

		BoundSql boundSql = delegate.getBoundSql();
		Object parameterObject = boundSql.getParameterObject();
		if (parameterObject == null) {
			throw new NullPointerException("parameterObject");
		}
		Connection connection = (Connection) ivk.getArgs()[0];
		String sql = boundSql.getSql();
		String countSql = "select count(0) from (" + sql + ") as a_";
		logger.debug("分页总数sql:" + countSql);
		PreparedStatement countStmt = connection.prepareStatement(countSql);
		BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(), countSql, boundSql.getParameterMappings(), parameterObject);
		setParameters(countStmt, mappedStatement, countBS, parameterObject);
		ResultSet rs = countStmt.executeQuery();
		int count = 0;
		if (rs.next()) {
			count = rs.getInt(1);
		}
		rs.close();
		countStmt.close();

		String pageSql = null;
		if (parameterObject instanceof PageComponent) {
			// 以VO形式传参数
			PageComponent<T> page = null;
			page = (PageComponent) parameterObject;
			page.setRecordCount(count);
			pageSql = generatePageSql(sql, page);
		} else if (parameterObject instanceof Map) {
			// 以map形式传参，
			Map<String, Object> map = (Map<String, Object>) parameterObject;
			if (map == null)
				map = new HashMap<String, Object>();
			map.put("recordCount", count);
			pageSql = generatePageSqlMap(sql, map);
		} else {
			PageComponent page = null;
			Field pageField = ReflectUtil.getFieldByFieldName(parameterObject, "pageIndex");
			if (pageField != null) {
				page = (PageComponent) ReflectUtil.getValueByFieldName(parameterObject, "pageIndex");
				if (page == null)
					page = new PageComponent();
				page.setRecordCount(count);
				ReflectUtil.setValueByFieldName(parameterObject, "pageIndex", page);
			} else {
				throw new NoSuchFieldException(parameterObject.getClass().getName());
			}
			pageSql = generatePageSql(sql, page);
		}
		logger.debug("分页sql:" + pageSql);
		ReflectUtil.setValueByFieldName(boundSql, "sql", pageSql);
		return ivk.proceed();
	}

	private void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql, Object parameterObject) throws SQLException {
		ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
		List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
		if (parameterMappings != null) {
			Configuration configuration = mappedStatement.getConfiguration();
			TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
			MetaObject metaObject = parameterObject == null ? null : configuration.newMetaObject(parameterObject);
			for (int i = 0; i < parameterMappings.size(); i++) {
				ParameterMapping parameterMapping = parameterMappings.get(i);
				if (parameterMapping.getMode() != ParameterMode.OUT) {
					Object value;
					String propertyName = parameterMapping.getProperty();
					PropertyTokenizer prop = new PropertyTokenizer(propertyName);
					if (parameterObject == null) {
						value = null;
					} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
						value = parameterObject;
					} else if (boundSql.hasAdditionalParameter(propertyName)) {
						value = boundSql.getAdditionalParameter(propertyName);
					} else if (propertyName.startsWith(ForEachSqlNode.ITEM_PREFIX) && boundSql.hasAdditionalParameter(prop.getName())) {
						value = boundSql.getAdditionalParameter(prop.getName());
						if (value != null) {
							value = configuration.newMetaObject(value).getValue(propertyName.substring(prop.getName().length()));
						}
					} else {
						value = metaObject == null ? null : metaObject.getValue(propertyName);
					}
					TypeHandler typeHandler = parameterMapping.getTypeHandler();
					if (typeHandler == null) {
						throw new ExecutorException("There was no TypeHandler found for parameter " + propertyName + " of statement " + mappedStatement.getId());
					}
					typeHandler.setParameter(ps, i + 1, value, parameterMapping.getJdbcType());
				}
			}
		}
	}

	private String generatePageSql(String sql, PageComponent page) {
		if (page != null && (dialect != null || !dialect.equals(""))) {
			StringBuffer pageSql = new StringBuffer();
			if ("mysql".equals(dialect)) {
				pageSql.append(sql);
				pageSql.append(" limit " + page.getPageIndex() + "," + page.getPageSize());
			} else if ("oracle".equals(dialect)) {
				pageSql.append("select * from (select tmp_tb.*,ROWNUM row_id from (");
				pageSql.append(sql);
				pageSql.append(")  tmp_tb where ROWNUM<=");
				pageSql.append(page.getPageIndex() + page.getPageSize());
				pageSql.append(") where row_id>");
				pageSql.append(page.getPageIndex());
			}
			return pageSql.toString();
		} else {
			return sql;
		}
	}

	private String generatePageSqlMap(String sql, Map<String, Object> map) {
		if (map != null && (dialect != null || !dialect.equals(""))) {
			StringBuffer pageSql = new StringBuffer();
			if ("mysql".equals(dialect)) {
				pageSql.append(sql);

				// 根据索引计算分页
				Integer startLimit = Integer.parseInt(map.get("pageIndex").toString()) * Integer.parseInt(map.get("pageSize").toString());
				pageSql.append(" limit " + startLimit + "," + map.get("pageSize"));

			} else if ("oracle".equals(dialect)) {
				pageSql.append("select * from (select tmp_tb.*,ROWNUM row_id from (");
				pageSql.append(sql);
				pageSql.append(")  tmp_tb where ROWNUM<=");
				pageSql.append(map.get("pageIndex").toString() + map.get("pageSize"));
				pageSql.append(") where row_id>");
				pageSql.append(map.get("pageIndex"));
			}
			return pageSql.toString();
		} else {
			return sql;
		}
	}
}
